package com.jt808.decode.utils;

import com.jt808.decode.constant.Jt808Constant;
import com.jt808.decode.constant.Jt808MessageIdEnum;
import com.jt808.decode.constant.Jt808RegisterResultEnum;
import com.jt808.decode.constant.Jt808ReplyResultEnum;
import com.jt808.decode.model.Jt808Message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * constant definition
 * @data 20230302
 */
@Slf4j
public class Jt808PacketUtil {
    private Jt808PacketUtil() {
    }

    /**
     * Handle JT808 message packets
     *
     * @param in
     * @return
     */
    public static Object decodeJt808Packet(ByteBuf in) {
        //The readable length cannot be less than the basic length
        if (in.readableBytes() < Jt808Constant.MSG_BASE_LENGTH) {
            return null;
        }

        //Prevent illegal code stream attacks. If the data is too large, it will be abnormal
        if (in.readableBytes() > Jt808Constant.MSG_MAX_LENGTH) {
            in.skipBytes(in.readableBytes());
            return null;
        }

        //Find the message header, and discard all data if not found
        in.markReaderIndex();
        int headerIndex = in.bytesBefore(Jt808Constant.MSG_HEAD_TAIL_FLAG);
        if (headerIndex < 0) {
            in.skipBytes(in.readableBytes());
            return null;
        }

        //If the data after the message header is less than the basic length, it will wait for the next packet to arrive before unpacking
        in.skipBytes(headerIndex);
        if (in.readableBytes() < Jt808Constant.MSG_BASE_LENGTH) {
            in.resetReaderIndex();
            return null;
        }

        //Find the message footer. If it is not found, continue to wait for the next package
        in.readByte();
        int tailIndex = in.bytesBefore(Jt808Constant.MSG_HEAD_TAIL_FLAG);
        if (tailIndex < 0) {
            in.resetReaderIndex();
            return null;
        }

        //If the data between the header and footer of the message is less than the basic length, it is abnormal data
        if (tailIndex < Jt808Constant.MSG_BASE_LENGTH - 2) {
            in.skipBytes(tailIndex);
            return null;
        }

        int bodyLen = tailIndex;
        //Create ByteBuf to store the data after de escape
        ByteBuf frame = ByteBufAllocator.DEFAULT.heapBuffer(bodyLen + 2);
        frame.writeByte(Jt808Constant.MSG_HEAD_TAIL_FLAG);
        //The data between the header and footer of the message is de escaped
        unescape(in, frame, bodyLen);
        in.readByte();
        frame.writeByte(Jt808Constant.MSG_HEAD_TAIL_FLAG);

        //The length after de escape cannot be less than the basic length
        if (frame.readableBytes() < Jt808Constant.MSG_BASE_LENGTH) {
            log.error("The length after de escape cannot be less than{},message:{}", Jt808Constant.MSG_BASE_LENGTH, ByteBufUtil.hexDump(frame));
            ReferenceCountUtil.release(frame);
            return null;
        }
        return frame;
    }

    /**
     * In the message header, message body and check code, 0x7D 0x02 is de escaped as 0x7E, and 0x7D 0x01 is de escaped as 0x7D
     *
     * @param in
     * @param frame
     * @param bodyLen
     */
    public static void unescape(ByteBuf in, ByteBuf frame, int bodyLen) {
        int i = 0;
        while (i < bodyLen) {
            int b = in.readUnsignedByte();
            if (b == 0x7D) {
                int nextByte = in.readUnsignedByte();
                if (nextByte == 0x01) {
                    frame.writeByte(0x7D);
                } else if (nextByte == 0x02) {
                    frame.writeByte(0x7E);
                } else {
                    //Abnormal data
                    frame.writeByte(b);
                    frame.writeByte(nextByte);
                }
                i += 2;
            } else {
                frame.writeByte(b);
                i++;
            }
        }
    }

    /**
     * In the message header, message body and check code, 0x7E is escaped as 0x7D 0x02, and 0x7D is escaped as 0x7D 0x01
     *
     * @param out
     * @param bodyBuf
     */
    public static void escape(ByteBuf out, ByteBuf bodyBuf) {
        while (bodyBuf.readableBytes() > 0) {
            int b = bodyBuf.readUnsignedByte();
            if (b == 0x7E) {
                out.writeShort(0x7D02);
            } else if (b == 0x7D) {
                out.writeShort(0x7D01);
            } else {
                out.writeByte(b);
            }
        }
    }

    /**
     * Response 0x8001
     * @param jt808Msg
     * @return
     */
    public static String reply8001(Jt808Message jt808Msg){
        ByteBuf msgBody = Unpooled.buffer(5);
        msgBody.writeShort(jt808Msg.getMsgFlowId());
        msgBody.writeShort(jt808Msg.getMsgId());
        msgBody.writeByte(Jt808ReplyResultEnum.SUCCESS.getValue());
        return sendToTerminal(Jt808MessageIdEnum.MSG_8001.getMessageId(),jt808Msg,msgBody);
    }

    /**
     * Terminal registration response
     *
     * @param jt808Msg
     * @param authCode
     * @return
     */
    public static String reply8100(Jt808Message jt808Msg, String authCode) {
        byte[] authCodeArr = authCode == null ? new byte[0] : authCode.getBytes();
        ByteBuf msgBody = Unpooled.buffer(3 + authCodeArr.length);
        msgBody.writeShort(jt808Msg.getMsgFlowId());
        msgBody.writeByte(Jt808RegisterResultEnum.SUCCESS.getValue());
        msgBody.writeBytes(authCodeArr);
        return sendToTerminal(Jt808MessageIdEnum.MSG_8100.getMessageId(), jt808Msg, msgBody);
    }

    /**
     * Response data packet
     * @param messageId
     * @param jt808Message
     * @param msgBody
     * @return
     */
    private static String sendToTerminal(int messageId, Jt808Message jt808Message,ByteBuf msgBody){
        Jt808Message replyMsg = new Jt808Message();
        replyMsg.setMsgId(messageId);
        replyMsg.setVersionFlag(jt808Message.getVersionFlag());
        replyMsg.setProtocolVersion(jt808Message.getProtocolVersion());
        replyMsg.setDeviceIdArr(jt808Message.getDeviceIdArr());
        replyMsg.setMsgFlowId(jt808Message.getMsgFlowId());
        replyMsg.setMsgBody(msgBody);
        return Jt808ProtocolEncoder.encode(replyMsg);
    }
}
